﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using XTYAPI.PLC;

namespace XTYAPI.通讯
{
    public class ModbusTcp
    {

        #region 属性字段

        //ModbusTcp:Modbus协议在TCP/IP网络上的应用

        //创建通信对象
        private Socket tcpClient;

        //发送超时
        public int SendTimeOut { get; set; } = 2000;

        public int ReceiveTimeOut { get; set; } = 2000;

        //从站地址
        public int SlaveAddress { get; set; } = 1;

        public int MaxCycleTimer { get; set; } = 5;

        //编码格式
        public DataFormat DataFormat { get; set; } = DataFormat.ABCD;

        //互斥锁
        private SimpleHybirdLock InteractiveLock = new SimpleHybirdLock();

        #endregion

        #region 建立及断开连接
        /// <summary>
        /// 建立连接
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口号</param>
        public void Connect(string ip, string port)
        {
            //实例化Socket
            tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //设置Socket属性
            tcpClient.SendTimeout = this.SendTimeOut;

            tcpClient.ReceiveTimeout = this.ReceiveTimeOut;

            //封装一个EndPoint对象
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));

            //建立连接
            tcpClient.Connect(endPoint);
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            if (tcpClient != null)
            {
                tcpClient.Close();
            }
        }
        #endregion

        #region 读取输出线圈  功能码0x01

        public byte[] ReadOutputStatus(int iAddress, int iLength)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 1 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文
            int length = 0;

            if (iLength % 8 == 0)
            {
                length = iLength / 8;
            }
            else
            {
                length = iLength / 8 + 1;
            }

            if (rcv != null && rcv.Length == 9 + length)
            {
                if (rcv[7] == 0x01 && rcv[8] == length)
                {
                    // 第五步：解析报文
                    return GetByteArray(rcv, 9, length);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 读取输入线圈  功能码0x02

        public byte[] ReadInputStatus(int iAddress, int iLength)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 2 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });


            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文
            int length = 0;

            if (iLength % 8 == 0)
            {
                length = iLength / 8;
            }
            else
            {
                length = iLength / 8 + 1;
            }

            if (rcv != null && rcv.Length == 9 + length)
            {
                if (rcv[7] == 0x02 && rcv[8] == length)
                {
                    // 第五步：解析报文
                    return GetByteArray(rcv, 9, length);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 读取保持寄存器   功能码0x03

        public byte[] ReadKeepReg(int iAddress, int iLength)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 3 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文
            if (rcv != null && rcv.Length == 9 + iLength * 2)
            {
                if (rcv[7] == 0x03 && rcv[8] == iLength * 2)
                {
                    // 第五步：解析报文
                    return GetByteArray(rcv, 9, iLength * 2);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 读取输入寄存器   功能码0x04

        public byte[] ReadInputReg(int iAddress, int iLength)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 4 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文
            if (rcv != null && rcv.Length == 9 + iLength * 2)
            {
                if (rcv[7] == 0x03 && rcv[8] == iLength * 2)
                {
                    // 第五步：解析报文
                    return GetByteArray(rcv, 9, iLength * 2);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }


        #endregion

        #region 预置单线圈   功能码0x05

        /// <summary>
        /// 强制单个线圈
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool ForceCoil(int iAddress, bool value)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 5 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            if (value)
            {
                SendCommand.Add(new byte[] { (byte)(0xFF), (byte)(0x00) });
            }
            else
            {
                SendCommand.Add(new byte[] { (byte)(0x00), (byte)(0x00) });
            }

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文

            if (rcv != null)
            {
                return ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }


        #endregion

        #region 预置单个寄存器   功能码0x06

        public bool PreSetSingleReg(int iAddress, short SetValue)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 6 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(GetByteArrayFrom16Bit(SetValue));

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                return ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }

        public bool PreSetSingleReg(int iAddress, ushort SetValue)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 6 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(GetByteArrayFrom16Bit(SetValue));

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                return ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region 预置多个线圈   功能码0x0F

        public bool ForceMultiCoil(int iAddress, bool[] SetValue)
        {
            byte[] iSetValue = BoolArrayToByteArray(SetValue);

            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0});

            int byteLength = 7 + iSetValue.Length;

            //字节长度
            SendCommand.Add((byte)(byteLength / 256));

            SendCommand.Add((byte)(byteLength %256));

            //单元标识符和功能码
            SendCommand.Add(new byte[] { (byte)SlaveAddress,0x0F});

            //起始线圈
            SendCommand.Add((byte)(iAddress / 256));

            SendCommand.Add((byte)(iAddress % 256));

            //线圈数量

            SendCommand.Add((byte)(SetValue.Length / 256));

            SendCommand.Add((byte)(SetValue.Length % 256));

            //字节计数
            SendCommand.Add((byte)(iSetValue.Length));

            //具体字节
            SendCommand.Add(iSetValue);

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                byte[] send = GetByteArray(SendCommand.array, 0, 12);
                send[4] = 0x00;
                send[5] = 0x06;

                return ByteArrayEquals(send, rcv);
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region 预置多个寄存器   功能码0x10

        //float   Int32  Uint32  float[]  Int32[]  UInt32[]         A  float  B int32  C  Uint32

        //字节数组

        public bool PreSetMultiByteArray(int iAddress, byte[] SetValue)
        {
            //首先判断一下字节数组是否正确

            if (SetValue == null || SetValue.Length == 0 || SetValue.Length % 2 == 1)
            {
                return false;
            }

            int RegLength = SetValue.Length / 2;

            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0 });

            int byteLength = 7 + SetValue.Length;

            //字节长度
            SendCommand.Add((byte)(byteLength / 256));

            SendCommand.Add((byte)(byteLength % 256));

            //单元标识符和功能码
            SendCommand.Add(new byte[] { (byte)SlaveAddress, 0x10 });

            //起始寄存器
            SendCommand.Add((byte)(iAddress / 256));

            SendCommand.Add((byte)(iAddress % 256));

            //寄存器数量

            SendCommand.Add((byte)(RegLength / 256));

            SendCommand.Add((byte)(RegLength % 256));

            //字节计数
            SendCommand.Add((byte)(SetValue.Length));

            //具体字节
            SendCommand.Add(SetValue);

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                byte[] send = GetByteArray(SendCommand.array, 0, 12);
                send[4] = 0x00;
                send[5] = 0x06;
                return ByteArrayEquals(send, rcv);
            }
            else
            {
                return false;
            }
        }

        public bool PreSetMultiReg(int iAddress, float SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom32Bit(SetValue));      
        }

        public bool PreSetMultiReg(int iAddress, int SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom32Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, uint SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom32Bit(SetValue));
        }

        /// <summary>
        /// 写入浮点型数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, float[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        /// <summary>
        /// 写入int数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, int[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        /// <summary>
        /// 写入uint数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, uint[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        // short  ushort  short[]  ushort[]  

        public bool PreSetMultiReg(int iAddress, short SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom16Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, ushort SetValue)
        {
            return PreSetMultiByteArray(iAddress, GetByteArrayFrom16Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, short[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom16Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        public bool PreSetMultiReg(int iAddress, ushort[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(GetByteArrayFrom16Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        #endregion

        #region 自定义截取字节数组
        /// <summary>
        /// 自定义截取字节数组
        /// </summary>
        /// <param name="dest"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        private byte[] GetByteArray(byte[] dest, int offset, int count)
        {
            if (dest != null && dest.Length >= offset + count)
            {
                byte[] result = new byte[count];
                Array.Copy(dest, offset, result, 0, count);
                return result;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region 判断两个字节数组是否完全一致

        private bool ByteArrayEquals(byte[] b1, byte[] b2)
        {
            if (b1 == null || b2 == null) return false;
            if (b1.Length == 0 || b2.Length == 0 || b1.Length != b2.Length) return false;
            for (int i = 0; i < b1.Length; i++)
            {
                if (b1[i] != b2[i])
                {
                    return false;
                }
            }
            return true;
        }

        #endregion

        #region 布尔数组转换成字节数组

        private byte[] BoolArrayToByteArray(bool[] val)
        {
            if (val == null || val.Length == 0) return null;

            int iByteArrLen = 0;

            if (val.Length % 8 == 0)
            {
                iByteArrLen = val.Length / 8;

            }
            else
            {
                iByteArrLen = val.Length / 8 + 1;
            }

            byte[] result = new byte[iByteArrLen];

            for (int i = 0; i < iByteArrLen; i++)
            {
                //比如我们布尔的长度是20,  1-8表示第一个字节，9-16表示第二个字节，剩下的4个布尔

                int total = i == iByteArrLen - 1 ? val.Length - 8 * i : 8;

                for (int j = 0; j < total; j++)
                {
                    result[i] = SetbitValue(result[i], j + 1, val[8 * i + j]);
                }
            }
            return result;
        }

        #endregion

        #region 给字节某个位赋值
        private byte SetbitValue(byte data, int index, bool val)
        {
            if (index > 8 || index < 1)
                return 0;
            int v = index < 2 ? index : (2 << (index - 2));
            return val ? (byte)(data | v) : (byte)(data & ~v);
        }

        #endregion

        #region 16位整型转换字节数组

        private byte[] GetByteArrayFrom16Bit(short SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[2];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    break;
            
                case DataFormat.BADC:
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
                default:
                    break;
            }
            return Res;
        }
        private byte[] GetByteArrayFrom16Bit(ushort SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[2];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    break;

                case DataFormat.BADC:
                case DataFormat.DCBA:
                    Res = ResTemp;
                    break;
                default:
                    break;
            }
            return Res;
        }

        #endregion

        #region 32位数据转换字节数组

        private byte[] GetByteArrayFrom32Bit(float SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.DCBA:
                    Res[0] = ResTemp[0];
                    Res[1] = ResTemp[1];
                    Res[2] = ResTemp[2];
                    Res[3] = ResTemp[3];
                    break;
                default:
                    break;
            }
            return Res;
        }


        private byte[] GetByteArrayFrom32Bit(int SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.DCBA:
                    Res[0] = ResTemp[0];
                    Res[1] = ResTemp[1];
                    Res[2] = ResTemp[2];
                    Res[3] = ResTemp[3];
                    break;
                default:
                    break;
            }
            return Res;
        }


        private byte[] GetByteArrayFrom32Bit(uint SetValue)
        {
            byte[] ResTemp = BitConverter.GetBytes(SetValue);

            byte[] Res = new byte[4];

            switch (this.DataFormat)
            {
                case DataFormat.ABCD:
                    Res[0] = ResTemp[3];
                    Res[1] = ResTemp[2];
                    Res[2] = ResTemp[1];
                    Res[3] = ResTemp[0];
                    break;
                case DataFormat.BADC:
                    Res[0] = ResTemp[2];
                    Res[1] = ResTemp[3];
                    Res[2] = ResTemp[0];
                    Res[3] = ResTemp[1];
                    break;
                case DataFormat.CDAB:
                    Res[0] = ResTemp[1];
                    Res[1] = ResTemp[0];
                    Res[2] = ResTemp[3];
                    Res[3] = ResTemp[2];
                    break;
                case DataFormat.DCBA:
                    Res[0] = ResTemp[0];
                    Res[1] = ResTemp[1];
                    Res[2] = ResTemp[2];
                    Res[3] = ResTemp[3];
                    break;
                default:
                    break;
            }
            return Res;
        }

        #endregion

        #region 读取并接受

        /// <summary>
        /// 发送并接受
        /// </summary>
        /// <param name="SendByte"></param>
        /// <returns></returns>
        private byte[] SendAndReceive(byte[] SendByte)
        {
            InteractiveLock.Enter();
            try
            {
                tcpClient.Send(SendByte);
                return ReadMessage();
            }
            catch (Exception)
            {
                return null;
            }
            finally
            {
                InteractiveLock.Leave();
            }
        }

        /// <summary>
        /// 读取缓冲区值
        /// </summary>
        /// <returns></returns>
        private byte[] ReadMessage()
        {
            int count = tcpClient.Available;
            int cycletimer = 0;
            while (count == 0)
            {
                count = tcpClient.Available;
                cycletimer++;
                Thread.Sleep(20);
                if (cycletimer > MaxCycleTimer)
                {
                    break;
                }
            }
            if (count == 0)
            {
                return null;
            }
            else
            {
                byte[] buffer = new byte[count];
                tcpClient.Receive(buffer, count, SocketFlags.None);
                return buffer;
            }
        }
        #endregion
    }
}
